package cn.ibizlab.core.oauth2.config;


import cn.ibizlab.core.oauth2.service.OauthClientDetailsService;
import cn.ibizlab.util.security.AuthUserServiceImpl;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import static cn.ibizlab.core.oauth2.constants.OAuth2Constant.*;

/**
 * 标准jwt模式登录转换为oauth2模式登录适配器：将标准jwt模式生成令牌模式改为由OAuth2生成访问令牌
 */
@Primary
@Service
@Slf4j
@ConditionalOnProperty(name = "ibiz.auth.security", havingValue = "oauth2", matchIfMissing = true)
public class OAuth2TokenAdapter extends AuthUserServiceImpl {

    @Autowired
    @Lazy
    DefaultTokenServices tokenServices;

    @Autowired
    @Lazy
    ClientDetailsService clientDetailsService;

    @Autowired
    @Lazy
    TokenStore tokenStore;



    /**
     * 生成token
     * @param userDetails
     * @return
     */
    @Override
    public String generateToken(UserDetails userDetails) {
        return generateAccessToken(userDetails).getValue();
    }

    @Override
    public OAuth2AccessToken generateAccessToken(UserDetails userDetails) {
        Map<String, String> parameters = new HashMap<>();
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(PREDEFINE_CLIENTDETAILS_ID);
        parameters.put(REQUEST_PARAM_USERNAME,userDetails.getUsername());
        parameters.put(REQUEST_PARAM_CLIENT_ID,clientDetails.getClientId());
        TokenRequest tokenRequest = new TokenRequest(parameters, clientDetails.getClientId(), clientDetails.getScope(), PREDEFINE_CLIENTDETAILS_GRANTTYPE_PASSWORD);
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()));
        oAuth2Authentication.setDetails(userDetails);
        return tokenServices.createAccessToken(oAuth2Authentication);
    }

    @Override
    public Object refreshAccessToken(String refreshTokenValue) {
        Map<String, String> parameters = new HashMap<>();
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(PREDEFINE_CLIENTDETAILS_ID);
        parameters.put(REQUEST_PARAM_CLIENT_ID,clientDetails.getClientId());
        TokenRequest tokenRequest = new TokenRequest(parameters, clientDetails.getClientId(), clientDetails.getScope(), PREDEFINE_CLIENTDETAILS_GRANTTYPE_PASSWORD);
        return tokenServices.refreshAccessToken(refreshTokenValue,tokenRequest);
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = {"ibzuaa_users"}, key = "#root.target.systemId+':getByUsername:'+#p0"),
            @CacheEvict(value = {"ibzuaa_users"}, key = "'glob:*getByUsername:'+#p0")
    })
    public void resetByUsername(String username) {
        log.info("重置用户{}缓存",username);
    }

    @Caching(evict = {
            @CacheEvict(value = {"ibzuaa_users"}, key = "#root.target.systemId+':getByUsername:'+#p0"),
            @CacheEvict(value = {"ibzuaa_users"}, key = "'glob:*getByUsername:'+#p0")
    })
    @Override
    public void logout(String username) {
        revokeToken(username,Integer.MAX_VALUE);
    }

    @Caching(evict = {
            @CacheEvict(value = {"ibzuaa_users"}, key = "#root.target.systemId+':getByUsername:'+#p0"),
            @CacheEvict(value = {"ibzuaa_users"}, key = "'glob:*getByUsername:'+#p0")
    })
    @Override
    public void revokeToken(String username, int minExpiresIn) {
        Map<String, String> parameters = new HashMap<>();

        Collection<OAuth2AccessToken> tokens = tokenStore.findTokensByClientId(username);
        if(!ObjectUtils.isEmpty(tokens)) {
            tokens.forEach(token->tokenServices.revokeToken(token.getValue()));
        }
        else {
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(PREDEFINE_CLIENTDETAILS_ID);
            parameters.put(REQUEST_PARAM_USERNAME,username);
            parameters.put(REQUEST_PARAM_CLIENT_ID,clientDetails.getClientId());
            TokenRequest tokenRequest = new TokenRequest(parameters, clientDetails.getClientId(), clientDetails.getScope(), PREDEFINE_CLIENTDETAILS_GRANTTYPE_PASSWORD);
            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,new UsernamePasswordAuthenticationToken(username,null));
            OAuth2AccessToken token = tokenServices.getAccessToken(oAuth2Authentication);
            if(token!=null && token.getExpiresIn()<minExpiresIn)
                tokenServices.revokeToken(token.getValue());
        }

    }


    /**
     * 验证token有效性
     * @param token
     * @param userDetails
     * @return
     */
    @Override
    public Boolean validateToken(String token, UserDetails userDetails) {
        OAuth2Authentication authentication = tokenServices.loadAuthentication(token);
        return authentication != null;
    }

    /**
     * 解析token获取用户信息
     * @param token
     * @return
     */
    @SneakyThrows
    @Override
    public String getUsernameFromToken(String token) {
        SignedJWT signedJWT = SignedJWT.parse(token);
        JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
        return claimsSet.getSubject();
    }

}
